﻿namespace Jsl.FxCop
{
    using System;
    using System.Linq;
    using System.Runtime.InteropServices;
    using Microsoft.VisualStudio.CodeAnalysis.Extensibility;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Microsoft.FxCop.Sdk;
using System.Drawing;

    [TestClass]
    public class TestReferenceParametersShouldHaveTheirValuesSet
    {
        private void No_reference_parameter(string x)
        {
        }

        [TestMethod]
        public void Methods_without_reference_parameters_are_OK()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(TestReferenceParametersShouldHaveTheirValuesSet), "No_reference_parameter")
                .HasNoMoreProblems();
        }

        private void Reference_parameter_that_is_set(ref string x)
        {
            x = "first";
            x = "second";
        }

        [TestMethod]
        public void Method_with_reference_parameter_that_is_set_is_OK()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(TestReferenceParametersShouldHaveTheirValuesSet), "Reference_parameter_that_is_set")
                .HasNoMoreProblems();
        }

        private void Refrence_parameter_passed_to_another_method_by_reference(ref string x)
        {
            Reference_parameter_that_is_set(ref x);
        }

        [TestMethod]
        public void Method_with_refrence_parameter_passed_to_another_method_by_reference_is_OK()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(TestReferenceParametersShouldHaveTheirValuesSet), "Refrence_parameter_passed_to_another_method_by_reference")
                .HasNoMoreProblems();
        }

        private void Reference_parameter_not_set(ref string x)
        {
            string y = x;
            string z = y;       // Force keeping y=x in release mode.
        }

        [TestMethod]
        public void Method_with_reference_parameter_not_set_has_problems()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(TestReferenceParametersShouldHaveTheirValuesSet), "Reference_parameter_not_set")
                .HasProblem(
                    100,
                    FixCategories.Breaking,
                    MessageLevel.Warning,
                    "Pass the 'x' parameter by value.")
                .HasNoMoreProblems();
        }

        private void Reference_parameter_passed_as_a_non_referenced_parameter(ref string x)
        {
            No_reference_parameter(x);
        }

        [TestMethod]
        public void Method_with_reference_parameter_passed_as_a_non_referenced_parameter()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(TestReferenceParametersShouldHaveTheirValuesSet), "Reference_parameter_passed_as_a_non_referenced_parameter")
                .HasProblem(
                    100,
                    FixCategories.Breaking,
                    MessageLevel.Warning,
                    "Pass the 'x' parameter by value.")
                .HasNoMoreProblems();
        }

        private interface Interface_with_reference_parameters
        {
            void DoSomething(ref string x);
        }

        [TestMethod]
        public void Interface_methods_are_ignored()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(TestReferenceParametersShouldHaveTheirValuesSet.Interface_with_reference_parameters), "DoSomething")
                .HasNoMoreProblems();
        }

        private class Implements_interface_method : Interface_with_reference_parameters
        {
            public void DoSomething(ref string x)
            {
            }
        }

        [TestMethod]
        public void Ignore_methods_that_implement_an_iterface()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(Implements_interface_method), "DoSomething")
                .HasNoMoreProblems();
        }

        private class Implements_interface_method2 : Interface_with_reference_parameters
        {
            void Interface_with_reference_parameters.DoSomething(ref string x)
            {
            }
        }

        [TestMethod]
        public void Ignore_explicitely_implemented_interface_methods()
        {
            var method = typeof(Implements_interface_method2)
                .GetTypeNode()
                .Members
                .OfType<Method>()
                .Where(m => m.Name.Name.EndsWith(".DoSomething"))
                .Single();

            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(method)
                .HasNoMoreProblems();
        }

        internal abstract class Target
        {
            public abstract void DoSomething(ref string x);
        }

        [TestMethod]
        public void Abstract_methods_are_ignored()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(Target), "DoSomething")
                .HasNoMoreProblems();
        }

        public delegate void Delegate_Method(ref string x);

        [TestMethod]
        public void Delegates_are_ignored()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(Delegate_Method), "Invoke")
                .HasNoMoreProblems();
        }

        private void Parameter_is_out(out string x)
        {
            x = "";
        }

        [TestMethod]
        public void Method_with_out_parameter_is_OK()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(TestReferenceParametersShouldHaveTheirValuesSet), "Parameter_is_out")
                .HasNoMoreProblems();
        }

        public struct SHFILEINFO
        {
            public IntPtr hIcon;
        }

        [DllImport("shell32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);

        [TestMethod]
        public void DllImports_are_ignored()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(TestReferenceParametersShouldHaveTheirValuesSet), "SHGetFileInfo")
                .HasNoMoreProblems();
        }

        private void Structs_are_OK(ref Point point)
        {
            point.X = 101;
        }

        [TestMethod]
        public void Struct_reference_parameters_are_OK()
        {
            new ReferenceParametersShouldHaveTheirValuesSet()
                .RunMethodRule(typeof(TestReferenceParametersShouldHaveTheirValuesSet), "Structs_are_OK")
                .HasNoMoreProblems();
        }
    }
}